package com.zte.claa.inficombo.csa.app.service.photo;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.zte.claa.inficombo.csa.app.dao.mysql.impl.CameraPhotoDataMapper;
import com.zte.claa.inficombo.csa.app.model.dev.DevBase;
import com.zte.claa.inficombo.csa.app.model.dev.DevTpExtData;
import com.zte.claa.inficombo.csa.app.model.dev.DevTypeModel;
import com.zte.claa.inficombo.csa.app.model.redis.DevMeteData;
import com.zte.claa.inficombo.csa.app.service.csa.impl.BaseCacheManager;
import com.zte.claa.inficombo.csa.app.service.photo.bean.CameraPhotoData;
import com.zte.claa.inficombo.csa.app.service.photo.bean.CameraPhotoModel;
import com.zte.claa.inficombo.csa.app.service.photo.bean.PhotoDataModel;
import com.zte.claa.inficombo.csa.app.service.photo.bean.PhotoFileBaseInfo;
import com.zte.claa.inficombo.csa.app.service.photo.bean.PhotoFileHBInfo;
import com.zte.claa.inficombo.csa.app.service.photo.bean.PhotoFragPacket;
import com.zte.claa.inficombo.csa.app.service.photo.bean.PhotoRecogReq2AIC;
import com.zte.claa.inficombo.csa.app.service.rptparse.ParserContainer;
import com.zte.claa.inficombo.csa.app.util.ImageUtils;
import com.zte.claa.inficombo.csa.app.util.JsonUtils;
import com.zte.claa.inficombo.csa.app.util.StrUtils;

/*
 * Author by 0283000106 
 */
@Service("photo")
public class PhotoDataDealServiceImpl implements PhotoDataDealService, InitializingBean, DisposableBean {
	// 日志.
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	public static final String PHOTO_FILE_PATH = "photo/";
		
	// sensoreui和PhotoDataModel的映射关系：key=sensoreui，value=PhotoDataModel
	private Map<String, PhotoDataModel> sensoreuiPhotoDataMap = null;
	
	// 过期图片数据检查定时器（针对异常情况，处理超时未合并的数据）
	private ScheduledExecutorService expDataCleanScheduler = null;
	
	@Value("${server.port}")
	private String curSvrPort;
	
	@Value("${server.ip}")
	private String curSvrIp;
	
	@Value("${csa.redis.channelname.aicRecogResult}")
	private String resptopic;
	
	@Autowired
	private CameraPhotoDataMapper photoDataMapper;
	
	@Autowired
	private BaseCacheManager cacheManager;
	
	@Autowired
	private ParserContainer parserContainer;
	
	@Override
	public void afterPropertiesSet() throws Exception {
		// 初始化图片数据缓存空间
		if(null == sensoreuiPhotoDataMap){
			sensoreuiPhotoDataMap = new HashMap<String, PhotoDataModel>();
		}
		
		// 启动过期图片数据检查定时器. 
		this.expDataCleanScheduler = Executors.newScheduledThreadPool(1);
		//分包数据过期清理.
		Runnable task = new Runnable(){
			@Override
			public void run() {
				checkExpiredData();
			}
		};
		// 每隔1个小时调度一次.
		this.expDataCleanScheduler.scheduleAtFixedRate(task, 10, 3600, TimeUnit.SECONDS);
		logger.info("[**]Expired Data timer check task is submitted and will be scheduled in 3600 seconds later!");	

	}
	
	               
	@Override
	public void destroy() throws Exception {
		try{
			if(this.sensoreuiPhotoDataMap != null) {
				this.sensoreuiPhotoDataMap.clear();
				this.sensoreuiPhotoDataMap = null;
				logger.info("sensoreuiPhotoDataMap release ok!");
			}
		}catch(Exception e){
			logger.error("destroy failed!", e);
		}
	}
	
	// 根据sensoreui创建新的图片对象
	public PhotoDataModel createPhotoDataModel(String sensoreui){
		PhotoDataModel photoDataModel = new PhotoDataModel();
		photoDataModel.setSensoreui(sensoreui);
		this.getSensoreuiPhotoDataMap().put(sensoreui, photoDataModel);
		return photoDataModel;
	}
	
	// 根据图片文件基本信息创建图片对象
	public PhotoDataModel dealAIDBaseInfo(String sensoreui, PhotoFileBaseInfo photebase){
		PhotoDataModel photoDataModel = this.getSensoreuiPhotoDataMap().get(sensoreui);
		if(null == photoDataModel){
			photoDataModel = new PhotoDataModel();
			photoDataModel.setSensoreui(sensoreui);
		}
		if(null != photoDataModel.getFragPacketMap()){
			photoDataModel.getFragPacketMap().clear();
		}
		photoDataModel.setPhotebase(photebase);
		this.getSensoreuiPhotoDataMap().put(sensoreui, photoDataModel);
		logger.info("dealAIDBaseInfo set photoDataModel,sensoreui:"+sensoreui);
		return photoDataModel;
	}
	
	// 兼容心跳信息创建图片对象
	public PhotoDataModel dealAIDHeartBeatMsg(String sensoreui, PhotoFileHBInfo photohb){
		PhotoDataModel photoDataModel = this.getSensoreuiPhotoDataMap().get(sensoreui);
		if(null == photoDataModel){
			photoDataModel = new PhotoDataModel();
			photoDataModel.setSensoreui(sensoreui);
		}
		photoDataModel.setPhotohb(photohb);
		this.getSensoreuiPhotoDataMap().put(sensoreui, photoDataModel);
		logger.info("dealAIDHeartBeatMsg set photoDataModel,sensoreui:"+sensoreui);
		return photoDataModel;
	}
	
	// 插入图片分片数据包
	public boolean insertPhotoFragPacket(String sensoreui, PhotoFragPacket fragPacket){
		if(null == this.sensoreuiPhotoDataMap){
			return false;
		}
		PhotoDataModel photoDataModel = this.sensoreuiPhotoDataMap.get(sensoreui);
		if(null == photoDataModel){
			return false;
		}
		// 如果已经存在了，则认为是分包已收到，不需要插入
		if(true == photoDataModel.getFragPacketMap().containsKey(fragPacket.getPSeq())){
			return true;
		}
		photoDataModel.getFragPacketMap().put(fragPacket.getPSeq(), fragPacket);
		return true;
	}
	
	// 根据sensoreui获取分片bitmap数据
	public byte[] getPhotoFragBitMap(String sensoreui){
		if(null == this.sensoreuiPhotoDataMap){
			return null;
		}
		PhotoDataModel photoDataModel = this.sensoreuiPhotoDataMap.get(sensoreui);
		if(null == photoDataModel){
			logger.info("getPhotoFragBitMap photoDataModel is null,sensoreui:"+sensoreui);
			return null;
		}
		PhotoFileBaseInfo photobase = photoDataModel.getPhotebase();
		if(null == photobase){
			logger.info("getPhotoFragBitMap photobase is null");
			return null;
		}
		Map<Integer, PhotoFragPacket> fragPacketMap = photoDataModel.getFragPacketMap();
		if(null == fragPacketMap){
			logger.info("getPhotoFragBitMap fragPacketMap is null");
			return null;
		}		
		int bitmaplen = 0;
		int flagnum = photobase.getFlagNum();
		if(0 == flagnum%8){
			bitmaplen = flagnum/8;
		}else{
			bitmaplen = flagnum/8 + 1;
		}
		if(0 == bitmaplen){
			logger.info("getPhotoFragBitMap bitmaplen is 0");
			return null;
		}
		byte[] bitmapbytes = new byte[bitmaplen];
		try {
			for (Map.Entry<Integer, PhotoFragPacket> entry : fragPacketMap.entrySet()){
				int packseqno = entry.getKey();
				int byteno = 0;
				int bitno = 0;
				byteno = packseqno/8;
				bitno = packseqno%8;
				//logger.info("getPhotoFragBitMap info.byteno="+byteno+",bitmaplen="+bitmaplen+",bitno="+bitno+",packseqno="+packseqno);
				if(byteno >= bitmaplen){	
					logger.info("getPhotoFragBitMap info.byteno bigger than bitmaplen,byteno="+byteno+",bitmaplen="+bitmaplen+",bitno="+bitno+",packseqno="+packseqno);
					return null;
				}				
				Byte bitmbyte = bitmapbytes[byteno];
				Byte bittemp = (byte) (1 << bitno);
				bitmbyte = (byte) (bitmbyte | bittemp);
				bitmapbytes[byteno] = bitmbyte;		
			}
			logger.info("getPhotoFragBitMap bitmaplen="+bitmaplen+", bitmapbytes="+StrUtils.bytes2hexstr(bitmapbytes));
		}catch(Exception e){
			logger.error("getPhotoFragBitMap failed: bitmaplen="+bitmaplen+", err="+e.getMessage());
		}
		return bitmapbytes;
	}
	
	public boolean isPhotoComplete(String sensoreui){
		if(null == this.sensoreuiPhotoDataMap){
			return false;
		}
		PhotoDataModel photoDataModel = this.sensoreuiPhotoDataMap.get(sensoreui);
		if(null == photoDataModel){
			return false;
		}
		PhotoFileBaseInfo photobase = photoDataModel.getPhotebase();
		if(null == photobase){
			return false;
		}
		Map<Integer, PhotoFragPacket> fragPacketMap = photoDataModel.getFragPacketMap();
		if(null == fragPacketMap){
			return false;
		}
		int flagnum = photobase.getFlagNum();
		for(int i=0; i<flagnum; i++){
			if(true != fragPacketMap.containsKey(i)){
				return false;
			}
		}		
		return true;
	}
	
	public boolean genPhotoFile(String sensoreui) throws IOException{
		logger.info("genPhotoFile, sensoreui:"+sensoreui);
		if(null == this.sensoreuiPhotoDataMap){
			return false;
		}
		PhotoDataModel photoDataModel = this.sensoreuiPhotoDataMap.get(sensoreui);
		if(null == photoDataModel){
			return false;
		}
		// 加锁互斥，避免生成多张图片
		synchronized (photoDataModel){
			PhotoFileBaseInfo photobase = photoDataModel.getPhotebase();
			if(null == photobase){
				return false;
			}
			Map<Integer, PhotoFragPacket> fragPacketMap = photoDataModel.getFragPacketMap();
			if(null == fragPacketMap){
				return false;
			}
			if(true != isPhotoComplete(sensoreui)){
				return false;
			}
			
			int filelen = photobase.getFileLength();
			int flagnum = photobase.getFlagNum();
			long timemills = System.currentTimeMillis();		
			String filenameext = ".jpeg";
			if(1 == photobase.getFileType()){
				filenameext = ".jpeg";
			}else if(2 == photobase.getFileType()){
				filenameext = ".jpg";
			}else{
				filenameext = ".jpeg";
			}
			String filename = sensoreui +"_"+photobase.getFileID()+"_"+timemills+filenameext;		
			byte[] allbytes = new byte[filelen];
			for(int i=0,j=0; i<flagnum && j<filelen; i++){
				PhotoFragPacket photefrag = fragPacketMap.get(i);
				if(null == photefrag){
					return false;
				}else{
					for(int k=0; k<photefrag.getPLength(); k++){
						allbytes[j] = photefrag.getContent()[k];
						j++;
					}
				}
			}
	        DevBase devbase = cacheManager.getDev(sensoreui);
	        if(null == devbase){
	        	logger.error("genPhotoFile devbase is not existed, deveui:"+sensoreui);
	        	return false;
	        }
			//根据扩展配置进行图片翻转
			String imgfmt = filenameext.substring(1);
			String devExtData = devbase.getDatajson();
			HashMap<String, Object> devExtDataMap = null;
			Double rotateangel = null;
			logger.info("genPhotoFile, devExtData:"+devExtData);
			if (devExtData != null && !devExtData.trim().equals("")) {
				try {
					devExtDataMap = JsonUtils.jsonStrToHashMap(devExtData);
					if(null != devExtDataMap){
						rotateangel = (Double)devExtDataMap.get("rotateangel");
						if(null != rotateangel && rotateangel != 0){
							allbytes = ImageUtils.Rotate(allbytes, rotateangel, imgfmt);
						}
					}
				} catch (Exception err) {
					logger.error("Get devextdata failed: " + devExtData + ", err=" + err.getMessage());
				}
			}
			
			//logger.info("genPhotoFile allbytes:"+StrUtils.bytes2hexstr(allbytes));
			String photodirpath = PHOTO_FILE_PATH;
			String realFilePath = PHOTO_FILE_PATH + filename;
			try{			
		        File photodir = new File(photodirpath);
		        if (!photodir.exists()) {
		        	photodir.mkdirs();
		        }
		        File imagefile = new File(realFilePath);
		        if (imagefile.exists()) {
		        	//imagefile.delete();
		        	logger.warn("genPhotoFile imagefile is existed, FilePath:"+realFilePath);
		        }
		        FileOutputStream out = new FileOutputStream(imagefile);
		        for (int i = 0; i < allbytes.length; i ++){
					out.write(allbytes[i]);
				}
		        out.close();
		        logger.info("genPhotoFile successfully, FilePath:"+realFilePath);
		        
	    	    // 将图片数据存入数据库
		        CameraPhotoData camPhotoData = new CameraPhotoData();
		        String photoid = photoDataModel.getPhotoid();
		        camPhotoData.setDeveui(sensoreui);
		        camPhotoData.setPhotoid(photoid);
		        camPhotoData.setPhototype(photobase.getFileType());
		        camPhotoData.setProjectid(devbase.getProjectId());
		        camPhotoData.setPhoto(allbytes);
		        // 插入图片数据
		        photoDataMapper.addCameraPhotoData(camPhotoData);
		        sendPhotoDataRecog2Aic(camPhotoData, devbase);
		        
				String devTypeId = devbase.getDevType();
				DevTypeModel dtm = this.cacheManager.getDevTypeModel(devTypeId);
				if(dtm == null || dtm.getDmList() == null || dtm.getBase() == null){
					logger.error("[*!!*]Can not get datamodel for devtype="+devTypeId);
					return false;
				}
				
				JSONObject metajsonobj = new JSONObject();
		        metajsonobj.put("photoid", photoid);		        
		        metajsonobj.put("result", "recognizing...");
		        if(null != rotateangel){
		        	metajsonobj.put("rotateangel", rotateangel);
		        }
		        // 兼容AID处理流程
				if((dtm.getBase().getFntype() & DevTpExtData.FN_TP_2) != 0){
			        PhotoFileHBInfo hbinfo = photoDataModel.getPhotohb();
			        if(null != hbinfo){
				        metajsonobj.put("cur_volt", hbinfo.getVoltage());
				        metajsonobj.put("rssi", hbinfo.getRssi());
				        metajsonobj.put("snr", hbinfo.getSnr());
			        }
				}
				parseAicMeteData(devbase, null, metajsonobj.toString(), photoid);		        
				// this.deletePhotoDataModel(sensoreui);     
			}catch (Exception e) {		
				logger.error("genPhotoFile failed, FilePath:"+realFilePath+",error:"+e.getMessage());
			}finally {
				this.deletePhotoDataModel(sensoreui);
			}
			return true;
		}
	}
	
    public void sendPhotoDataRecog2Aic(CameraPhotoData camPhotoData, DevBase devbase) throws Exception{
    	if(null == devbase){
    		logger.error("DevBase is not existed");
			return;
    	}
    	
    	if(null == camPhotoData){
    		logger.error("camPhotoData is not existed, deveui:"+devbase.getDevEui());
			return;
    	}
    	
    	String devTypeId = devbase.getDevType();
		DevTypeModel dtm = this.parserContainer.getCacheManager().getDevTypeModel(devTypeId);
		if(dtm == null || dtm.getDmList() == null){
			logger.error("[*!!*]Can not get datamodel for devtype="+devTypeId);
			return;
		}
		PhotoRecogReq2AIC photoRecogReq = new PhotoRecogReq2AIC();
		JSONObject recogReqJson = new JSONObject(); 
		String restfulurl = "http://"+this.curSvrIp+":"+this.curSvrPort+"/inficombo/apis/meters?token=Claa2017&imageid="+camPhotoData.getPhotoid();
		photoRecogReq.setDeveui(camPhotoData.getDeveui());
		photoRecogReq.setMetertype(devbase.getDevType());
		photoRecogReq.setReporttime(camPhotoData.getCollecttime());
		photoRecogReq.setImageid(camPhotoData.getPhotoid());
		recogReqJson.put("deveui", camPhotoData.getDeveui());
		recogReqJson.put("metertype", devbase.getDevType());
		recogReqJson.put("reporttime", camPhotoData.getCollecttime());
		recogReqJson.put("imageid", camPhotoData.getPhotoid());
		recogReqJson.put("restfulurl", restfulurl);
		recogReqJson.put("resptopic", resptopic);
		
		//String modelid = dtm.getBase().getRemark();
		String modelid = devbase.getDevType();
		CameraPhotoModel photoRecogModel = photoDataMapper.queryCameraPhotoModel(modelid);
		if(null == photoRecogModel || null == photoRecogModel.getModelconf() || "".equals(photoRecogModel.getModelconf())){
			logger.error("[*!!*]Photo recog model is null, devtype="+devTypeId);
			return;
		}
		String modelconf = photoRecogModel.getModelconf();		
		JSONObject modelJSONObject = null;
		JSONArray  modelJsonArray = null;		
        Object resObj = new JSONTokener(modelconf).nextValue();
        if(resObj instanceof JSONObject){
        	modelJSONObject = new JSONObject(modelconf);
        	recogReqJson.put("dialmodel", modelJSONObject);
        }else if(resObj instanceof JSONArray){
        	modelJsonArray = new JSONArray(modelconf);
        	recogReqJson.put("dialmodel", modelJsonArray);
        }
        
        String pubMsg = recogReqJson.toString();
		this.parserContainer.getAicPhotoAnalyPublisher().publishMessage(pubMsg);
		logger.info("[*^_^*]Publish aic analyse request message ok: "+pubMsg);
        
    }
	
	// 根据sensoreui删除图片对象
	public boolean deletePhotoDataModel(String sensoreui){
		if(null == this.sensoreuiPhotoDataMap){
			return false;
		}
		PhotoDataModel photoDataModel = this.sensoreuiPhotoDataMap.get(sensoreui);
		if(null == photoDataModel){
			return true;
		}
		photoDataModel.getFragPacketMap().clear();
		this.sensoreuiPhotoDataMap.remove(sensoreui);
		return true;
	}


	public Map<String, PhotoDataModel> getSensoreuiPhotoDataMap() {
		if(null == sensoreuiPhotoDataMap){
			this.sensoreuiPhotoDataMap = new HashMap<String, PhotoDataModel>();
		}
		return this.sensoreuiPhotoDataMap;
	}

	// 判断数据是否已经超时
	private boolean isTimeout(Timestamp dataOrgRptTime){
		return (System.currentTimeMillis() - dataOrgRptTime.getTime()) > (24*60*60*1000);
	}
	
	private void checkExpiredData(){
		if(null == this.sensoreuiPhotoDataMap){
			return;
		}
//		for (Map.Entry<String, PhotoDataModel> entry : this.sensoreuiPhotoDataMap.entrySet()){
//			try {
//				PhotoDataModel data = entry.getValue();
//				if (data == null) {
//					continue;
//				}
//				if (this.isTimeout(data.getDataOrgRptTime())) {
//					this.sensoreuiPhotoDataMap.remove(entry.getKey());
//				}
//			}catch(Exception e){
//				logger.error("checkExpiredData failed: sensoreui="+entry.getKey()+", err="+e.getMessage());
//			}
//		}
//		bug:Using invalid iterator
		Iterator<Map.Entry<String, PhotoDataModel>> iter = this.sensoreuiPhotoDataMap.entrySet().iterator();
		while (iter.hasNext()){
			Map.Entry<String, PhotoDataModel> entry = iter.next();
			try {
				PhotoDataModel data = entry.getValue();
				if (data == null || data.getDataOrgRptTime() == null){
					continue;
				}
				if (this.isTimeout(data.getDataOrgRptTime())){
					iter.remove();
				}
			} catch (Exception e) {
				logger.error("checkExpiredData failed: sensoreui=" + entry.getKey() + ", err=" + e.getMessage());
			}
		}
		
	}
	
	public static boolean genPhotoFileByImageIO(byte[] allbytes){
    	String filename = "1@004a7700ffffffff_14";
    	String filenameext = ".jpg";		
    	String photodirpath = PHOTO_FILE_PATH;
		String realFilePath = PHOTO_FILE_PATH + filename + filenameext;
		 
		try{			
	        File photodir = new File(photodirpath);
	        if (!photodir.exists()) {
	        	photodir.mkdirs();
	        }
			ByteArrayInputStream byteArrInStream = new ByteArrayInputStream(allbytes); 
			BufferedImage bufImage = ImageIO.read(byteArrInStream);				
			File imagefile = new File(realFilePath);
			/*
	        if (imagefile.exists()) {
	        	imagefile.delete();
	        }
	        */
	        boolean issucc = ImageIO.write(bufImage, "jpeg", imagefile);
	        System.out.println("issucc:"+issucc);
		}catch (Exception e) {		
			return false;
		}
		return true;
    }
    
    public static boolean genPhotoFileByWriteFile(byte[] allbytes){
    	String filename = "1@004a7700ffffffff_14";
    	String filenameext = ".jpg";	
    	long timemills = System.currentTimeMillis();
    	String photodirpath = PHOTO_FILE_PATH;
		String realFilePath = PHOTO_FILE_PATH + filename + filenameext;
		realFilePath = PHOTO_FILE_PATH + filename + timemills + filenameext;
		try{			
	        File photodir = new File(photodirpath);
	        if (!photodir.exists()) {
	        	photodir.mkdirs();
	        }
	        File imagefile = new File(realFilePath);
	        if (imagefile.exists()) {
	        	imagefile.delete();
	        }
	        FileOutputStream out = new FileOutputStream(imagefile);
	        for (int i = 0; i < allbytes.length; i ++)
			{
				out.write(allbytes[i]);
			}
	        out.close();
		}catch (Exception e) {		
			System.out.println("out:"+e.getMessage());
			
		}
		return true;
    }
    
	private void parseAicMeteData(DevBase dev, String rpttime, String rptData, String photoid) throws Exception {
		String devTypeId = dev.getDevType();
		DevTypeModel dtm = this.parserContainer.getCacheManager().getDevTypeModel(devTypeId);
		if(dtm == null || dtm.getDmList() == null){
			logger.error("[*!!*]Can not get datamodel for devtype="+devTypeId);
			return;
		}
		Map<String, Object> mdata = JsonUtils.jsonStrToHashMap(rptData.toString());
		DevMeteData meteData = new DevMeteData();
		if(null!=rpttime && !"".equals(rpttime)){
			meteData.setRpttime(new Timestamp(sdf.parse(rpttime).getTime()));
		}
		meteData.setAppeui(dev.getAppEui());
		meteData.setDeveui(dev.getDevEui());
		meteData.setDevname(dev.getDevName());
		meteData.setDevtype(devTypeId);
		meteData.setPort("0");
		meteData.setProjectid(dev.getProjectId());
		meteData.setMdata(mdata);	
			
		// 持久化数据.
		this.parserContainer.getPersisService().saveAicDevMeteData(meteData, dtm.getBase().getTbname(),photoid);
		// 5.构造广播数据，进行redis发布...
		String pubMsg = JsonUtils.toJson(meteData);
		this.parserContainer.getUpdataPublisher().publishMessage(pubMsg);
		logger.info("[*^_^*]Publish metedata message ok: "+pubMsg);
	}

}
